package main

import "fmt"

/*
								Go 语言切片(Slice)
------------------------------------------------------------------------------------------
Go 语言切片是对数组的抽象。
Go 数组的长度不可改变，在特定场景中这样的集合就不太适用，Go中提供了一种灵活，功能强悍的内置类型切片("动态数组"),
与数组相比切片的长度是不固定的，可以追加元素，在追加时可能使切片的容量增大。

------------------------------------------------------------------------------------------
我们先看看切片的结构定义，reflect.SliceHeader：
type SliceHeader struct {
    Data uintptr
    Len  int
    Cap  int
}
可以看出切片的开头部分和Go字符串是一样的，但是切片多了一个Cap成员表示切片指向的内存空间的最大容量（对应元素的个数，而不是字节数）

让我们看看切片有哪些定义方式：
var (
    a []int               // nil切片, 和 nil 相等, 一般用来表示一个不存在的切片
    b = []int{}           // 空切片, 和 nil 不相等, 一般用来表示一个空的集合
    c = []int{1, 2, 3}    // 有3个元素的切片, len和cap都为3
    d = c[:2]             // 有2个元素的切片, len为2, cap为3
    e = c[0:2:cap(c)]     // 有2个元素的切片, len为2, cap为3
    f = c[:0]             // 有0个元素的切片, len为0, cap为3
    g = make([]int, 3)    // 有3个元素的切片, len和cap都为3
    h = make([]int, 2, 3) // 有2个元素的切片, len为2, cap为3
    i = make([]int, 0, 3) // 有0个元素的切片, len为0, cap为3
)
和数组一样，内置的len函数返回切片中有效元素的长度，内置的cap函数返回切片容量大小，容量必须大于或等于切片的长度

切片可以和nil进行比较，只有当切片底层数据指针为空时切片本身为nil，这时候切片的长度和容量信息将是无效的。
如果有切片的底层数据指针为空，但是长度和容量不为0的情况，那么说明切片本身已经被损坏了
（比如直接通过reflect.SliceHeader或unsafe包对切片作了不正确的修改）。
------------------------------------------------------------------------------------------

定义切片
你可以声明一个未指定大小的数组来定义切片：

var identifier []type
切片不需要说明长度。

或使用make()函数来创建切片:

var slice1 []type = make([]type, len)

也可以简写为

slice1 := make([]type, len)
也可以指定容量，其中capacity为可选参数。

make([]T, length, capacity)
-----------------------------------------------------------------------------------------
切片初始化
s :=[] int {1,2,3 }
直接初始化切片，[]表示是切片类型，{1,2,3}初始化值依次是1,2,3.其cap=len=3

s := arr[:]
初始化切片s,是数组arr的引用

s := arr[startIndex:endIndex]
将arr中从下标startIndex到endIndex-1 下的元素创建为一个新的切片

s := arr[startIndex:]
默认 endIndex 时将表示一直到arr的最后一个元素

s := arr[:endIndex]
默认 startIndex 时将表示从arr的第一个元素开始

s1 := s[startIndex:endIndex]
通过切片s初始化切片s1

s :=make([]int,len,cap)
---------------------------------------------------------------------------------------------
len() 和 cap() 函数
切片是可索引的，并且可以由 len() 方法获取长度。

切片提供了计算容量的方法 cap() 可以测量切片最长可以达到多少。

---------------------------------------------------------------------------------------------
空(nil)切片
一个切片在未初始化之前默认为 nil，长度为 0，
---------------------------------------------------------------------------------------------
切片截取
可以通过设置下限及上限来设置截取切片 [lower-bound:upper-bound]

---------------------------------------------------------------------------------------------
append() 和 copy() 函数
如果想增加切片的容量，我们必须创建一个新的更大的切片并把原分片的内容都拷贝过来。

下面的代码描述了从拷贝切片的 copy 方法和向切片追加新元素的 append 方法。

var a = []int{1,2,3}
a = append([]int{0}, a...)        // 在开头添加 1 个元素
a = append([]int{-3,-2,-1}, a...) // 在开头添加 1 个切片

在开头一般都会导致内存的重新分配，而且会导致已有的元素全部复制 1 次。因此，从切片的开头添加元素的性能一般要比从尾部追加元素的性能差很多。

删除切片元素

根据要删除元素的位置有三种情况：从开头位置删除，从中间位置删除，从尾部删除。其中删除切片尾部的元素最快：


a = []int{1, 2, 3}
a = a[:len(a)-1]   // 删除尾部 1 个元素
a = a[:len(a)-N]   // 删除尾部 N 个元素
删除开头的元素可以直接移动数据指针：


a = []int{1, 2, 3}
a = a[1:] // 删除开头 1 个元素
a = a[N:] // 删除开头 N 个元素
删除开头的元素也可以不移动数据指针，但是将后面的数据向开头移动。可以用 append 原地完成（所谓原地完成是指在原有的切片数据对应的内存区间内完成，不会导致内存空间结构的变化）：


a = []int{1, 2, 3}
a = append(a[:0], a[1:]...) // 删除开头 1 个元素
a = append(a[:0], a[N:]...) // 删除开头 N 个元素
也可以用 copy 完成删除开头的元素：


copy: 将第二个参数的元素复制到第一个参数的开始位置，返回复制的元素个数

a = []int{1, 2, 3}
a = a[:copy(a, a[1:])] // 删除开头 1 个元素
a = a[:copy(a, a[N:])] // 删除开头 N 个元素

*/
func printSlice(x []int) {
	fmt.Printf("len=%d cap=%d slice=%v\n", len(x), cap(x), x)
}

/**
append和copy
切片高效操作的要点是要降低内存分配的次数，尽量保证append操作不会超出cap的容量，降低触发内存分配的次数和每次分配内存大小。
*/
func test234() {
	var numbers []int
	printSlice(numbers)

	/* 允许追加空切片 */
	numbers = append(numbers, 0)
	printSlice(numbers)

	/* 向切片添加一个元素 */
	numbers = append(numbers, 1)
	printSlice(numbers)

	/* 同时添加多个元素 */
	numbers = append(numbers, 2, 3, 4)
	printSlice(numbers)

	/* 创建切片 numbers1 是之前切片的两倍容量*/
	numbers1 := make([]int, len(numbers), (cap(numbers))*2)

	/* 拷贝 numbers 的内容到 numbers1 */
	copy(numbers1, numbers)
	printSlice(numbers1)
}

/**
切片截取
*/
func test233() {
	/* 创建切片 */
	numbers := []int{0, 1, 2, 3, 4, 5, 6, 7, 8}
	printSlice(numbers)

	/* 打印原始切片 */
	fmt.Println("numbers ==", numbers)

	/* 打印子切片从索引1(包含) 到索引4(不包含)*/
	s := numbers[1:4]
	fmt.Println("numbers[1:4] ==", s)
	s[2] = 100 //numbers 也会修改
	fmt.Println(numbers)

	/* 默认下限为 0*/
	fmt.Println("numbers[:3] ==", numbers[:3])

	/* 默认上限为 len(s)*/
	fmt.Println("numbers[4:] ==", numbers[4:])

	numbers1 := make([]int, 0, 5)
	printSlice(numbers1)

	/* 打印子切片从索引  0(包含) 到索引 2(不包含) */
	number2 := numbers[:2]
	printSlice(number2)

	/* 打印子切片从索引 2(包含) 到索引 5(不包含) */
	number3 := numbers[2:5]
	printSlice(number3)
}

/**
空(nil)切片
*/
func test232() {
	var numbers []int

	printSlice(numbers)

	if numbers == nil {
		fmt.Printf("切片是空的")
	}
}

/**
切片初始化
*/
func test231() {
	var s = []int{1, 2, 3} // 切片初始化  数组初始化 arr = [4]int{1, 2, 3, 4} ，数组必须指定大小 切片不用
	fmt.Println(s)

	fmt.Println(len(s))
	fmt.Println(cap(s))
	s = append(s, 4)
	fmt.Println(len(s))
	fmt.Println(cap(s))
}

/**
切片作为参数，引用传递
*/
func test235(s []int) {
	s[0] = 1
}

func test236() {
	s := []int{10, 20}
	test235(s)
	fmt.Println(s)
}
func main23() {
	//test231()
	//test232()
	//test233()
	//test234()
	//test236()
}
